'''This module contains the interface for the "tactical" game, specifically for
controlling one of the player characters in a dungeon setting.


Definitional ideas...

    app(
        button('Hello, world', on_click=replace('text', 'clicked!'))
        para('This is some text', id='text')
    )

or:

    class app(app):
        button('Hello, world', on_click=replace('text', 'clicked!'))
        text = para('This is some text')


or:

    app = Root()
    Button(app, 'Hello, world!', replace('text', 'clicked'))
    Text(app, 'This is some text', id='text')

or:

    app = Root().add(
        Button('hello'),
        Text("text")
    )
'''

import math
import pyglet
import cocos
from cocos.director import director
from cocos.actions import *

TOP = 'top'
BOTTOM = 'bottom'
LEFT = 'left'
RIGHT = 'right'
CENTER = 'center'
FILL = 'fill'

intceil = lambda i: int(math.ceil(i))

BACKGROUND_GROUP = pyglet.graphics.OrderedGroup(0)
QUADS_GROUP = pyglet.graphics.OrderedGroup(1)
LINES_GROUP = pyglet.graphics.OrderedGroup(2)
IMAGES_GROUP = pyglet.graphics.OrderedGroup(3)
TEXT_GROUP = pyglet.graphics.OrderedGroup(4)

# The intention with styles is that they'd be inherited unless explicitly overridden,
# including things like background attributes.
class Style(dict):
    def copy(self):
        s = Style(**self)
        s['background'] = s['background'].copy()
        s['button_background'] = s['button_background'].copy()
        return s

    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        if self.get('_locked') and name not in self:
            raise AttributeError('this style is closed to new attributes')
        self[name] = value

    def lock(self):
        self['_locked'] = True

class RectStyle(object):
    def __init__(self, fill_color, border_color, hpad=0, vpad=0):
        self.fill_color = fill_color
        self.border_color = border_color
        self.hpad = hpad
        self.vpad = vpad

    def copy(self):
        return RectStyle(self.fill_color, self.border_color,
            self.hpad, self.vpad)

    def create(self, x, y, width, height, batch):
        return DrawnRect(self, x, y, width, height, batch)

class DrawnRect(object):
    bg = bo = None
    def __init__(self, style, x, y, width, height, batch):
        self.style = style
        self.x, self.y = x, y
        self.width, self.height = width, height
        x2, y2 = x + width + style.hpad * 2, y + height + style.vpad * 2
        if style.fill_color:
            self.bg = batch.add(4, pyglet.gl.GL_QUADS,
                QUADS_GROUP,
                ('v2i', (x, y, x2, y, x2, y2, x, y2)),
                ('c4B', style.fill_color * 4),
            )
        if style.border_color:
            # draw at extent minus one pixel (filling doesn't fill the extent
            # pixel, so don't draw border there either)
            y2 -= 1
            x2 -= 1
            self.bo = batch.add(8, pyglet.gl.GL_LINES, LINES_GROUP,
                ('v2i', (x, y, x2, y, x2, y, x2, y2+1, x2, y2, x, y2, x, y2, x, y)),
                ('c4B', style.border_color * 8),
            )

    def delete(self):
        if self.bg is not None:
            self.bg.delete()
        if self.bo is not None:
            self.bo.delete()
        self.bg = self.bo = None

    def move(self, x, y):
        self.x, self.y = x, y
        w, h = self.width, self.height
        x2, y2 = x + w + self.style.hpad*2, y + h + self.style.vpad*2
        if self.bg is not None:
            self.bg.vertices[:] = (x, y, x2, y, x2, y2, x, y2)
        if self.bo is not None:
            y2 -= 1
            x2 -= 1
            self.bo.vertices[:] = (x, y, x2, y, x2, y, x2, y2+1, x2, y2, x, y2,
                    x, y2, x, y)

    def resize(self, w, h):
        x, y = self.x, self.y
        self.width, self.height = w, h
        x2, y2 = x + w + self.style.hpad*2, y + h + self.style.vpad*2
        if self.bg is not None:
            self.bg.vertices[:] = (x, y, x2, y, x2, y2, x, y2)
        if self.bo is not None:
            y2 -= 1
            x2 -= 1
            self.bo.vertices[:] = (x, y, x2, y, x2, y, x2, y2+1, x2, y2, x, y2,
                    x, y2, x, y)

    def hover(self):
        pass

    def press(self):
        pass

    def focus(self):
        pass

default = Style()
default.background = RectStyle((255, 255, 255, 255), None)
default.text = dict(font_name='Arial', font_size=12, color=(0, 0, 0, 255), bold=False,
    italic=False, multiline=False, dpi=None)
default.text_italic = dict(italic=True)
default.text_bold = dict(bold=True)
default.button_background = RectStyle((200, 200, 200, 255), (0, 0, 0, 255), hpad=4, vpad=2)
default.button_hover_background = (220, 220, 220, 255)
default.button_press_background = (250, 250, 200, 255)
default.textinput_background = RectStyle(None, (0, 0, 0, 255), hpad=4, vpad=2)
default.lock()

class TextureGroup(pyglet.graphics.Group):
    def __init__(self, texture, parent=None):
        super(TextureGroup, self).__init__(parent)
        self.texture = texture

    def set_state(self):
        pyglet.gl.glEnable(self.texture.target)
        pyglet.gl.glBindTexture(self.texture.target, self.texture.id)

    def unset_state(self):
        pyglet.gl.glDisable(self.texture.target)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.texture)

    def __eq__(self, other):
        return (other.__class__ is self.__class__ and
                self.parent is other.parent and
                self.texture.target == other.texture.target and
                self.texture.id == other.texture.id)

    def __hash__(self):
        return hash((id(self.parent),
                     self.texture.id, self.texture.target))

class RepeatedTileStyle(object):
    def __init__(self, image):
        self.image = image

    def copy(self):
        return RepeatedTileStyle(self.image)

    def create(self, x, y, width, height, batch):
        return RepeatedTileRect(self.image, x, y, width, height, batch)

class RepeatedTileRect(object):
    bg = bo = None
    def __init__(self, image, x, y, width, height, batch):
        self.x, self.y = x, y
        self.width, self.height = width, height

        # load the image manually so it's not atlas'ed and thus is tileable
        self.image = pyglet.image.load(image, file=pyglet.resource.file(image))
        self.group = TextureGroup(self.image.texture, QUADS_GROUP)
        x2, y2 = x + self.width, y + self.height
        # XXX texture coords so we handle non-rect textures?
        tx2 = self.width / float(self.image.width)
        ty2 = self.height / float(self.image.height)
        self.bg = batch.add(4, pyglet.gl.GL_QUADS,
            self.group,
            ('v2i', (x, y, x2, y, x2, y2, x, y2)),
            ('c4B', (255, 255, 255, 255) * 4),
            ('t2f', (0, 0, tx2, 0, tx2, ty2, 0, ty2)),
        )

    def delete(self):
        if self.bg is not None:
            self.bg.delete()
        self.bg = None

    def move(self, x, y):
        # no need to update texture
        self.x, self.y = x, y
        x2, y2 = x + self.width, y + self.height
        self.bg.vertices[:] = (x, y, x2, y, x2, y2, x, y2)

    def resize(self, w, h):
        # update vertices and texture coordinates
        x, y = self.x, self.y
        self.width, self.height = w, h
        x2, y2 = x + w, y + h
        self.bg.vertices[:] = (x, y, x2, y, x2, y2, x, y2)
        tx2 = w / float(self.image.width)
        ty2 = h / float(self.image.height)
        self.bg.tex_coords[:] = (0, 0, tx2, 0, tx2, ty2, 0, ty2)

    def hover(self):
        pass

    def press(self):
        pass

    def focus(self):
        pass


# XXX snarf a reference to the styled object and any child proxies so we can
# alter appearance on style changes
class StyleProxy(dict):
    def __init__(self, base):
        self['_base'] = base
    def __getitem__(self, name):
        if name in self: return super(StyleProxy, self).__getitem__(name)
        return self['_base'][name]
    def __getattr__(self, name):
        if name in self: return self[name]
        return self['_base'][name]
    def __setattr__(self, name, value):
        self[name] = value
    def copy(self):
        copy = StyleProxy(None)
        for name in self:
            copy[name] = self[name]
            if name == '_base':
                copy[name] = self[name].copy()
            else:
                copy[name] = self[name]
        return copy


class Widget(object):
    '''
    Event handlers, if not None:
        on_click
        on_hover
        on_drag -- this element is draggable. on_drag must return a non-None
                   object (that optionally renders itself and has .x and .y)
                   XXX check the "renders itself" bit
        on_drop -- invoked with a draggable when it is dropped

    '''
    _width_from_image = False
    _height_from_image = False
    _width_from_text = False
    _height_from_text = False
    is_focusable = None
    def __init__(self, parent, x, y, width, height, id=None,
            image=None, text=None, style=None,
            on_click=None, on_drag=None, on_drop=None, on_hover=None):
        self.parent_widget = parent
        self.widgets = []
        self.id = id
        self._x, self._y = x, y
        if width is None:
            width = parent.width
        if height is None:
            height = parent.height
        self._width, self._height = width, height

        if style is None:
            style = StyleProxy(parent.style)
        self.style = style

        # create the background
        self.background = self.style.background.create(self._x, self._y,
            self._width, self._height, self.get_root_widget().batch)

        # this will create a pyglet.Sprite
        self._image = image

        # this will create a pyglet.text.Label
        self._text = text

        # event handlers
        self.on_click = on_click
        self.on_drag = on_drag
        self.on_drop = on_drop
        self.on_hover = on_hover

        if parent is not None:
            parent.add_widget(self)

    def __repr__(self):
        return '<%s %s at (%s,%s) size (%s,%s)>'%(self.__class__.__name__,
            self.id, self._x, self._y, self._width, self._height)

    def __contains__(self, point):
        x, y = point
        ix, iy = self.x, self.y
        if x < ix: return False
        if y < iy: return False
        w, h = self.width, self.height
        if x > ix + w: return False
        if y > iy + h: return False
        return True

    def __iter__(self):
        return iter(list(self.widgets))

    def remove_widget(self, widget):
        self.widgets.remove(widget)

    def event(self, handler):
        name = handler.__name__
        if name not in ('on_click', 'on_drag', 'on_drop', 'on_hover'):
            raise ValueError('Unknown handler %s'%name)
        setattr(self, name, handler)
        return handler

    auto_id = 0
    def add_widget(self, widget):
        root = self.get_root_widget()
        if widget.id is None:
            widget.id = 'auto-%s'%root.auto_id
            root.auto_id += 1
        self.widgets.append(widget)
        root.by_id[widget.id] = widget
        return widget

    def find_child(self, point, implements):
        '''Find the first child widget (depth-first) that:
            - if point is not None then contains that pixel point
            - if implements is supplied then it contains a sequence of strings
              that nominate non-None atttributes that the child must provide
              at least one of
        '''
        for widget in self.widgets:
            if point is not None and point not in widget:
                continue

            if widget.widgets:
                child = widget.find_child(point, implements)
                if child:
                    return child

            if not implements:
                return widget

            for interface in implements:
                if getattr(widget, interface) is not None:
                    return widget

    def set_x(self, value):
        value = int(value)
        if value == self._x: return
        self._x = value
        self.background.move(self._x, self._y)
        if self._image is not None:
            self._image.x = value + self.style.background.hpad
        if self._text is not None:
            self._text.x = value + self.style.background.hpad
    x = property(lambda s:s._x, set_x)

    def set_y(self, value):
        value = int(value)
        if value == self._y: return
        self._y = value
        self.background.move(self._x, self._y)
        if self._image is not None:
            self._image.y = value + self.style.background.vpad
        if self._text is not None:
            self._text.y = value + self.style.background.vpad
    y = property(lambda s:s._y, set_y)

    def set_position(self, value):
        self.x, self.y = value
    position = property(lambda s: (s._x, s._y), set_position)

    def set_width(self, value):
        self._width = value
        self.background.resize(self._width, self._height)
    width = property(lambda s: s._width + s.style.background.hpad * 2, set_width)

    def set_height(self, value):
        self._height = value
        self.background.resize(self._width, self._height)
    height = property(lambda s: s._height + s.style.background.vpad * 2, set_height)

    # XXX sets the image but gets a pyglet.sprite.Sprite
    def set_image(self, image):
        self._image.image = image
    image = property(lambda s:s._image, set_image)

    # XXX sets the text but gets a pyglet.text.Label
    def set_text(self, text):
        # XXX doesn't alter the label's dimensions
        self._text.text = text
        if self._width_from_text:
            self.set_width(self._text.content_width + self.style.background.hpad * 2)
        if self._height_from_text:
            self.set_height(self._text.content_height + self.style.background.vpad * 2)
    text = property(lambda s:s._text, set_text)

    def get_color(self):
        if self._text is not None:
            return self._text.color
        if self._image is not None:
            return self._image.rgb + (self._image.opacity,)
        return (255, 255, 255, 255)
    def set_color(self, value):
        if self._image is not None:
            rgb = value[:3]
            a = vaue[3]
            if self._image.color != rgb:
                self._image.color = rgb
            if self._image.opacity != a:
                self._image.opacity = a
        if self._text is not None:
            if self._text.color != value:
                self._text.color = value
    color = property(lambda s:s._color, set_color)

    def clear(self):
        for widget in list(self.widgets):
            widget.delete()

    def delete(self):
        # clean up other things' references to me
        self.parent_widget.remove_widget(self)
        root = self.get_root_widget()
        del root.by_id[self.id]

        if self.background is not None:
            self.background.delete()
            self.background = None

        # now clean up my references to other things
        if self.image is not None:
            self._image.delete()
            self._image = None
        if self.text is not None:
            self._text.delete()
            self._text = None
        for child in list(self.widgets):
            child.delete()
        self.parent_widget = None

    def get_root_widget(self):
        return self.parent_widget.get_root_widget()

class Label(Widget):
    def __init__(self, parent, x=0, y=0, width=None, height=None,
            id=None, image=None, text=None, style=None,
            font_size=None, font_name=None, dpi=None,
            italic=False, bold=False,   # XXX title, ...?
            on_click=None, on_drag=None, on_drop=None,**kw):

        root = parent.get_root_widget()

        if style is None:
            style = parent.style

        # inner coordinate
        ix = x + style.background.hpad
        iy = y + style.background.vpad

        if image is not None:
            if isinstance(image, basestring):
                image = pyglet.resource.image(image)
            kw['batch'] = root.batch
            scale = kw.pop('scale', 1)
            if 'group' not in kw:
                kw['group'] = IMAGES_GROUP
            image = pyglet.sprite.Sprite(image, ix, iy, **kw)
            if scale != 1:
                image.scale = scale
            if width is None:
                width = image.width + style.background.hpad * 2
                self._width_from_image = True
            if height is None:
                height = image.height + style.background.vpad * 2
                self._height_from_image = True
        if text is not None:
            if kw.get('multiline') and width is None:
                width = parent.width
            iwidth = width
            iheight = height
            if iwidth is not None:
                iwidth -= style.background.hpad * 2
            if iheight is not None:
                iheight -= style.background.vpad * 2

            # XXX scale = dpi change?
            tkw = parent.style.text.copy()
            if bold:
                tkw.update(parent.style.text_bold)
            elif italic:
                tkw.update(parent.style.text_italic)
            if font_size is not None:
                tkw['font_size'] = font_size
            if font_name is not None:
                tkw['font_name'] = font_name
            if dpi is not None:
                tkw['dpi'] = dpi
            tkw.update(
                batch = root.batch,
                x = ix,
                y = iy,
                width = iwidth,
                height = iheight,
                anchor_y = kw.get('anchor_y', 'bottom'),
                anchor_x = kw.get('anchor_x', 'left'),
                multiline = kw.get('multiline', False),
                group = TEXT_GROUP,
            )
            text = pyglet.text.Label(text, **tkw)
            if width is None:
                width = text.content_width + style.background.hpad * 2
                self._width_from_text = True
            if height is None:
                height = text.content_height + style.background.vpad * 2
                self._height_from_text = True

        super(Label, self).__init__(parent, x, y, width, height, id=id,
            image=image, text=text, style=style,
            on_click=on_click, on_drag=on_drag, on_drop=on_drop)

class Button(Label):
    def __init__(self, parent, text, on_click, **kw):
        kw.update(text=text, on_click=on_click)
        if 'style' not in kw:
            style = StyleProxy(parent.style)
            style.background = style.button_background
            kw['style'] = style
        super(Button, self).__init__(parent, **kw)

class ImageButton(Label):
    def __init__(self, parent, image, on_click, **kw):
        kw.update(image=image, on_click=on_click)
        super(ImageButton, self).__init__(parent, **kw)

class Text(Label):
    def __init__(self, parent, text, **kw):
        kw.update(text=text)
        super(Text, self).__init__(parent, **kw)

class Image(Label):
    def __init__(self, parent, image, **kw):
        kw.update(image=image)
        super(Image, self).__init__(parent, **kw)

class MyCaret(pyglet.text.caret.Caret):
    '''Override the standard Caret to be more picky about the events
    it ruthlessly gobbles up.
    '''
    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
        return super(MyCaret, self).on_mouse_scroll(x, y, scroll_x, scroll_y)

    def on_mouse_press(self, x, y, button, modifiers):
        lx, ly = self._layout._x, self._layout._y
        if x < lx: return False
        if y < ly: return False
        if x > lx + self._layout.width: return False
        if y > ly + self._layout.height: return False
        if not self._textinput._focused:
            self._textinput.focus = True
        return super(MyCaret, self).on_mouse_press(x, y, button, modifiers)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        #if self.mark is None and out_of_bounds:
        #   return False
        return super(MyCaret, self).on_mouse_drag(x, y, dx, dy, buttons, modifiers)

    def on_activate(self):
        # don't return HANDLED
        super(MyCaret, self).on_activate()

    def on_deactivate(self):
        # don't return HANDLED
        super(MyCaret, self).on_deactivate()

    def delete(self):
        self._textinput = None
        super(MyCaret, self).delete()

class TextInput(Widget):
    is_focusable = True
    def __init__(self, parent, text, x=0, y=0, width=None, height=None,
            id=None, style=None,
            font_size=None, font_name=None, dpi=None,
            italic=False, bold=False,
            **kw):

        if style is None:
            style = StyleProxy(parent.style)
            style.background = style.textinput_background

        root = parent.get_root_widget()

        # inner coordinate
        ix = x + style.background.hpad
        iy = y + style.background.vpad

        if kw.get('multiline') and width is None:
            width = parent.width
        iwidth = width
        iheight = height
        if iwidth is not None:
            iwidth -= style.background.hpad * 2
        if iheight is not None:
            iheight -= style.background.vpad * 2

        # XXX scale = dpi change?
        tkw = parent.style.text.copy()
        if bold:
            tkw.update(parent.style.text_bold)
        elif italic:
            tkw.update(parent.style.text_italic)
        if font_size is not None:
            tkw['font_size'] = font_size
        if font_name is not None:
            tkw['font_name'] = font_name
        if dpi is not None:
            tkw['dpi'] = dpi
        tkw.update(
            batch = root.batch,
            width = iwidth,
            height = iheight,
            anchor_y = kw.get('anchor_y', 'bottom'),
            anchor_x = kw.get('anchor_x', 'left'),
            multiline = kw.get('multiline', False),
            group = TEXT_GROUP,
        )

        self.document = pyglet.text.decode_text(text)
        self.document.set_style(0, len(self.document.text), {
            'font_name': tkw['font_name'],
            'font_size': tkw['font_size'],
            'bold': tkw['bold'],
            'italic': tkw['italic'],
            'color': tkw['color'],
            'halign': 'left',
        })

        text = pyglet.text.layout.IncrementalTextLayout(self.document, 
            width=tkw['width'], height=tkw['height'], multiline=tkw['multiline'], 
            dpi=tkw['dpi'], batch=tkw['batch'], group=tkw['group'])

        text._x = ix
        text._y = iy
        text._anchor_x = tkw['anchor_x']
        text._anchor_y = tkw['anchor_y']
        text._update()

        if width is None:
            width = text.content_width + style.background.hpad * 2
            self._width_from_text = True
        if height is None:
            height = text.content_height + style.background.vpad * 2
            self._height_from_text = True

        # XXX maybe support on_drag one day?

        super(TextInput, self).__init__(parent, x, y, width, height, id=id,
            text=text, style=style,
            on_click=self.on_click)

    def on_click(self, w, x, y, button, modifiers):
        self.focus = True
        self._caret.on_mouse_press(x, y, button, modifiers)

    _focused = False
    def set_focus(self, focused):
        self._focused = focused
        if focused:
            # add a caret and make it active
            self.get_root_widget().set_focused_widget(self)
            self._caret = MyCaret(self._text,
                color=self.style.text['color'][:3])
            self._caret._textinput = self
            director.window.push_handlers(self._caret)
        else:
            # remove the caret
            self.get_root_widget().set_focused_widget(None)
            director.window.remove_handlers(self._caret)
            self.visible = self._active = False
            self._caret.delete()
            self.text.set_selection(0, 0)
    focus = property(lambda s: s._focus, set_focus)

    def delete(self):
        if self._focused:
            self.focus = False
            self._caret.delete()


class Spacer(Widget):
    def __init__(self, parent, x=0, y=0, width=None, height=None, **kw):
        # XXX remember these weren't set for later resize
        if width is None:
            width = parent.width
        if height is None:
            height = parent.height
        super(Spacer, self).__init__(parent, x, y, width, height, **kw)

class Layout(Widget):
    def add_widget(self, widget):
        super(Layout, self).add_widget(widget)
        self.layout()
    def remove_widget(self, widget):
        super(Layout, self).remove_widget(widget)
        self.layout()

    def set_x(self, value):
        value = int(value)
        if value == self._x: return
        self._x = value
        self.layout()
    x = property(lambda s:s._x, set_x)

    def set_y(self, value):
        value = int(value)
        if value == self._y: return
        self._y = value
        self.layout()
    y = property(lambda s:s._y, set_y)

    def set_width(self, value):
        value = int(value)
        if value == self._width: return
        self._width = value
        self.layout()
    width = property(lambda s:s._width, set_width)

    def set_height(self, value):
        value = int(value)
        if value == self._height: return
        self._height = value
        self.layout()
    height = property(lambda s:s._height, set_height)

class Column(Layout):
    '''Packs added widgets from the top down.

        Arguments are as per Widget. 
        (parent, id, x, y, width, height, ...)
    '''
    def __init__(self, parent, x=0, y=0, width=None, height=None, halign=LEFT, valign=TOP,
            spacing=0, **kw):
        # XXX remember these weren't set for later resize
        if width is None:
            width = parent.width
        if height is None:
            height = parent.height

        self.halign = halign
        self.valign = valign
        self.spacing = spacing

        super(Column, self).__init__(parent, x, y, width, height,
            **kw)

    @property
    def content_height(self):
        return sum(widget.height for widget in self.widgets)

    def layout(self):
        if not self.widgets:
            return
        # Determine starting X coord
        if self.halign == LEFT:
            x = self.x
        if self.halign == CENTER:
            x = self.x + self.width//2
        elif self.halign == RIGHT:
            x = self.x + self.width

        fill_spacing = self.spacing

        # height of this column's contents
        if self.valign == FILL:
            h = self.height
        else:
            h = sum(c.height for c in self.widgets) + self.spacing * (len(self.widgets)-1)

        # vertical align for this column
        if self.valign == BOTTOM:
            y = self.y + h
        elif self.valign == TOP:
            y = self.y + self.height
        elif self.valign == CENTER:
            y = self.y + self.height//2
        elif self.valign == FILL:
            y = self.y + self.height
            if len(self.widgets) == 1:
                fill_spacing = 0
            else:
                h = sum(c.height for c in self.widgets)
                fill_spacing = (self.height - h)/float(len(self.widgets)-1)

        # now layout the columns
        for child in self.widgets:
            y -= child.height
            child.y = int(y)
            y -= fill_spacing
            if self.halign == LEFT:
                child.x = int(x)
            elif self.halign == CENTER:
                child.x = int(x - child.width//2)
            elif self.halign == RIGHT:
                child.x = int(x - child.width)

    # XXX set_x/set_y/set_width/set_height to set all children


class Row(Layout):
    '''Packs added widgets from the top down.

        Arguments are as per Widget. 
        (parent, id, x, y, width, height, ...)
    '''
    def __init__(self, parent, x=0, y=0, width=None, height=None, halign=LEFT, valign=TOP,
            spacing=0, **kw):
        # XXX remember these weren't set for later resize
        if width is None:
            width = parent.width
        if height is None:
            height = parent.height

        self.halign = halign
        self.valign = valign
        self.spacing = spacing

        super(Row, self).__init__(parent, x, y, width, height, **kw)

    def layout(self):
        if not self.widgets:
            return

        # Determine starting Y coordinate
        if self.valign == BOTTOM:
            y = self.y
        elif self.valign == CENTER:
            y = self.y + self.height//2
        elif self.valign == TOP:
            y = self.y + self.height

        fill_spacing = self.spacing

        # width of this row's contents
        if self.halign == FILL:
            w = self.width
        else:
            w = sum(c.width for c in self.widgets) + self.spacing * (len(self.widgets)-1)

        # horizontal align for this row
        x = self.x
        if self.halign == RIGHT:
            x = self.x + self.width - w
        elif self.halign == CENTER:
            x = self.x + self.width//2
        elif self.halign == FILL:
            if len(self.widgets) == 1:
                fill_spacing = 0
            else:
                w = sum(c.width for c in self.widgets)
                fill_spacing = (self.width - w)/float(len(self.widgets)-1)

        for i, child in enumerate(self.widgets):
            if fill_spacing and i == len(self.widgets) - 1: 
                child.x = int(self.width - child.width)
            else:
                child.x = int(x)
                x += int(child.width + fill_spacing)
            if self.valign == BOTTOM:
                child.y = int(y)
            elif self.valign == CENTER:
                child.y = int(y - child.height//2)
            elif self.valign == TOP:
                child.y = int(y - child.height)


class Root(Layout, cocos.layer.Layer):
    is_event_handler = True
    def __init__(self, x=0, y=0, width=None, height=None, style=default):
        w, h = director.get_window_size()
        if width is None:
            width = w
        if height is None:
            height = h

        self.by_id = {}
        self.batch = pyglet.graphics.Batch()

        # invoke all base class constructors
        Layout.__init__(self, None, x, y, width, height, style=style)
        cocos.layer.Layer.__init__(self)

        self.background = style.background.create(x, y, w, h, self.batch)

    def layout(self):
        # XXX layout sub things
        pass

    def get_root_widget(self):
        return self

    __click = None
    def on_mouse_press(self, x, y, buttons, modifiers):
        if not buttons & pyglet.window.mouse.LEFT:
            return False

        self.__click = None
        self.__dragging = self.__dragged = None

        point = (x, y)
        widget = self.find_child(point, ('on_click', 'on_drag'))
        if widget is None:
            return False
        if widget.on_click is not None:
            self.__click = widget
        if widget.on_drag is not None:
            self.__dragging = widget
            self.__drag_start = point
        return True

    def on_mouse_release(self, x, y, button, modifiers):
        if not button & pyglet.window.mouse.LEFT:
            # LMB is still held down
            return False

        point = (x, y)

        if self.__dragging is not None:
            if self.__dragged is not None:
                # we actually dragged far enough
                widget = self.find_child(point, ('on_drop', ))
                if widget is not None:
                    widget.on_drop(widget, self.__dragging)
                self.__dragged.delete()
            self.__dragging = self.__dragged = None
            return True

        widget = self.find_child(point, ('on_click', 'on_drop'))
        if widget is None or widget is not self.__click:
            self.__click = None
            return False

        if self.__click is not None:
            self.__click.on_click(self.__click, x, y, button, modifiers)
            self.__click = None

        return True

    __over = None
    def on_mouse_motion(self, x, y, dx, dy):
        point = (x, y)
        widget = self.find_child(point, ('on_hover', ))
        if widget is not self.__over:
            if self.__over is not None:
                self.__over.on_hover(False)
            self.__over = widget
            if self.__over is not None:
                self.__over.on_hover(True)
        return True

    __dragging = None       # the widget implmenting on_drag
    __dragged = None        # an widget used to visually show the dragging
    __drag_start = None     # the mouse location at the first on_mouse_press
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        if not self.__dragging:
            # do the mouse-over highlighting
            self.on_mouse_motion(x, y, dx, dy)
            return True

        # only drag if we've enough delta
        dx, dy = self.__drag_start
        if abs(x - dx) + abs(y - dy) < 2:
            return True

        # we are actually dragging
        if self.__dragged is None:
            self.__dragged = self.__dragging.on_drag(self.__dragging, x, y)
            cursor = director.window.get_system_mouse_cursor(director.window.CURSOR_HAND)
            director.window.set_mouse_cursor(cursor)
        else:
            self.__dragged.x = x
            self.__dragged.y = y

        # XXX check for on_drag_over
        return True

    def on_enter(self):
        super(Root, self).on_enter()
        focusable = self.find_child(None, ('is_focusable', ))
        if focusable:
            focusable.focus = True

    def on_exit(self):
        super(Root, self).on_exit()
        if self.__dragged is not None:
            # we're dragging so reset the mouse cursor
            director.window.set_mouse_cursor()
        self.__dragging = self.__dragged = None
        for child in self.widgets:
            child.delete()
        self.by_id = {}

    _focused_widget = None
    def set_focused_widget(self, widget):
        '''Invoked *only* by a Widget.set_focus.
        '''
        if widget is not None:
            if self._focused_widget:
                self._focused_widget.focus = False
            self._focused_widget = widget
        else:
            self._focused_widget = None

    def draw(self):
        self.batch.draw()

#
# utilities
#
def replace(id, text):
    def _replace(widget, x, y, b, m, id=id, text=text):
        widget.get_root_widget().by_id[id].text = text
    return _replace


def generic_drag_text(text):
    def on_drag(widget, x, y, text=text):
        batch = widget.get_root_widget().batch
        return pyglet.text.Label(text, x=x, y=y, batch=batch,
            color=(200, 50, 50, 255))
    return on_drag


